/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */ 
#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

void print(int attr, int key, string value)
{
  switch (attr) 
  {
  case 4:
    break;
  case 1:  // SELECT key
    fprintf(stdout, "%d\n", key);
    break;
  case 2:  // SELECT value
    fprintf(stdout, "%s\n", value.c_str());
    break;
  case 3:  // SELECT *
    fprintf(stdout, "%d '%s'\n", key, value.c_str());
    break;
  }
}


RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  BTreeIndex tree;
  IndexCursor cursor;
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;
  bool   index_on = 1;

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }

  //open the index
  if (rc = tree.open(table + ".idx", 'r') < 0)
    index_on = 0;

  if (index_on)
  {
    int intEqualTo = -1;
    string stringEqualTo = "";
    vector<int> intNotEqualTo;
    vector<string> stringNotEqualTo;
    int intLowerBound = -1;
    int intUpperBound = -1;
    string stringLowerBound = "";
    bool lowerEqualOn = 0;
    string stringUpperBound = "";
    bool upperEqualOn = 0;
    for (unsigned i = 0; i < cond.size(); i++)
    {
      //cout << cond[i].attr << ' ' << cond[i].value << endl;
      if (cond[i].comp == SelCond::EQ)
      {
        if (cond[i].attr == 1)
        {
          if (rc = tree.locate(atoi(cond[i].value), cursor, 0, 0))
            goto exit_select;
		  if (intEqualTo != -1 && intEqualTo != atoi(cond[i].value))
		  {
			if (attr == 4)
			  fprintf(stdout, "0\n");
		    goto exit_select;
		  }
          intEqualTo = atoi(cond[i].value);
        }
        else
		{
		  if (stringEqualTo != "" && stringEqualTo != cond[i].value)
		  {
			if (attr == 4)
			  fprintf(stdout, "0\n");
		    goto exit_select;
		  }
          stringEqualTo = cond[i].value;
        }
	  }
      else if (cond[i].comp == SelCond::NE)
      {
        if (cond[i].attr == 1)
          intNotEqualTo.push_back(atoi(cond[i].value));
        else
          stringNotEqualTo.push_back(cond[i].value);
      }
      else if (cond[i].comp == SelCond::GT)
      {
        if (cond[i].attr == 1 && atoi(cond[i].value) >= intLowerBound)
          intLowerBound = atoi(cond[i].value) + 1;
        else if (cond[i].attr == 2 && (cond[i].value >= stringLowerBound || stringLowerBound == ""))
        {
          stringLowerBound = cond[i].value;
          lowerEqualOn = 0;
        }
      }
      else if (cond[i].comp == SelCond::LT)
      {
          if (cond[i].attr == 1 && (atoi(cond[i].value) <= intUpperBound || intUpperBound == -1))
            intUpperBound = atoi(cond[i].value) - 1;
          else if (cond[i].attr == 2 && (cond[i].value <= stringUpperBound || stringUpperBound == ""))
          {
            stringUpperBound = cond[i].value;
            upperEqualOn = 0;
          }
      }
      else if (cond[i].comp == SelCond::GE)
      {
          if (cond[i].attr == 1 && atoi(cond[i].value) > intLowerBound)
            intLowerBound = atoi(cond[i].value);
          else if (cond[i].attr == 2 && (cond[i].value > stringLowerBound || stringLowerBound == ""))
          {
            stringLowerBound = cond[i].value;
            lowerEqualOn = 1;
          }
      }
      else if (cond[i].comp == SelCond::LE)
      {
          if (cond[i].attr == 1 && (atoi(cond[i].value) <= intUpperBound || intUpperBound == -1))
            intUpperBound = atoi(cond[i].value);
          else if (cond[i].attr == 2 && (cond[i].value < stringUpperBound || stringUpperBound == ""))
          {
            stringUpperBound = cond[i].value;
            upperEqualOn = 1;
          }
      }
    }
    //cout << intLowerBound << ' ' << intUpperBound << ' ' << endl;
    //cout << stringLowerBound << ' ' << stringUpperBound << ' ' << endl;
    if (intEqualTo == -1 && intLowerBound == -1 && intUpperBound == -1 && attr != 4)
      goto indexOff; //trololololol
    if (intEqualTo != -1)
    {
      rc = tree.locate(intEqualTo, cursor, 0, 0);
      if (rc)
      {
        goto exit_select;
      }
      tree.readForward(cursor, key, rid);
      if (key != intEqualTo)
      {
        if (attr == 4)
          fprintf(stdout, "0\n");
        goto exit_select;
      }
      if ((stringEqualTo != "" || stringUpperBound != "" ||
          stringLowerBound != "" || stringNotEqualTo.size() != 0 || attr == 2 || attr == 3) && 
          (rc = rf.read(rid, key, value) < 0))
      {
        fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
        goto exit_select;
      }
      if (stringEqualTo != "" && value != stringEqualTo)
      {
        if (attr == 4)
          fprintf(stdout, "0\n");
        goto exit_select;
      }
      if (stringUpperBound != "" &&
          ((upperEqualOn && value > stringUpperBound) ||
          (!upperEqualOn && value >= stringUpperBound)))
      {
        if (attr == 4)
          fprintf(stdout, "0\n");
        goto exit_select;
      }
      if (stringLowerBound != "" &&
          ((lowerEqualOn && value < stringLowerBound) ||
          (!lowerEqualOn && value <= stringLowerBound)))
      {
        if (attr == 4)
          fprintf(stdout, "0\n");
        goto exit_select;
      }
      for (unsigned i = 0; i < stringNotEqualTo.size(); i++)
      {
        if (value == stringNotEqualTo[i])
        {
          if (attr == 4)
            fprintf(stdout, "0\n");
          goto exit_select;
        }
      }
      if (attr == 4) 
        fprintf(stdout, "1\n");
      else
        print(attr, key, value);
      goto exit_select;
    }
    count = 0;
    rid.pid = rid.sid = 0;
	  if (intLowerBound == -1)
		  intLowerBound = 0;
    rc = tree.locate(intLowerBound, cursor, 0, 0);
    //cout << "made it here" << endl;
    if (rc)
      return rc;
    //cout << "made it here" << endl;
    rc = tree.readForward(cursor, key, rid);
    bool done = 0;
    while (key <= intUpperBound || intUpperBound == -1)
    {
      if ((stringEqualTo != "" || stringUpperBound != "" ||
          stringLowerBound != "" || stringNotEqualTo.size() != 0 || attr == 2 || attr == 3) && 
          (rc = rf.read(rid, key, value) < 0))
      {
        fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
        goto exit_select;
      }
      if (stringEqualTo != "" && value != stringEqualTo)
      {
        if (done)
          break;
        rc = tree.readForward(cursor, key, rid);
        if (rc)
          done = 1;
        continue;
      }
      if (stringUpperBound != "" &&
          ((upperEqualOn && value > stringUpperBound) ||
          (!upperEqualOn && value >= stringUpperBound)))
      {
        if (done)
          break;
        rc = tree.readForward(cursor, key, rid);
        if (rc)
          done = 1;
        continue;
      }
      if (stringLowerBound != "" &&
          ((lowerEqualOn && value < stringLowerBound) ||
          (!lowerEqualOn && value <= stringLowerBound)))
      {
        if (done)
          break;
        rc = tree.readForward(cursor, key, rid);
        if (rc)
          done = 1;
        continue;
      }
      bool found = false;
      for (unsigned i = 0; i < stringNotEqualTo.size(); i++)
      {
        if (value == stringNotEqualTo[i])
        {
          if (done)
            break;
          rc = tree.readForward(cursor, key, rid);
          if (rc)
            done = 1;
          found = true;
          break;
        }
      }
      if (found)
        continue;
      found = false;
      for (unsigned i = 0; i < intNotEqualTo.size(); i++)
      {
        if (key == intNotEqualTo[i])
        {
          if (done)
            break;
          rc = tree.readForward(cursor, key, rid);
          if (rc)
            done = 1;
          found = true;
          break;
        }
      }
      if (found)
        continue;
      count++;
      print(attr, key, value);
      if (done)
        break;
      rc = tree.readForward(cursor, key, rid);
      if (rc)
        done = 1;
    }
    if (attr == 4)
    {
      fprintf(stdout, "%d\n", count);
    }
    rc = 0;
    goto exit_select;
  }
  else
  {
    indexOff:

    // scan the table file from the beginning
    rid.pid = rid.sid = 0;
    count = 0;
    while (rid < rf.endRid())
    {
      // read the tuple
      if ((rc = rf.read(rid, key, value)) < 0)
      {
        fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
        goto exit_select;
      }

      // check the conditions on the tuple
      for (unsigned i = 0; i < cond.size(); i++) 
      {
        // compute the difference between the tuple value and the condition value
        switch (cond[i].attr) 
        {
        case 1:
    diff = key - atoi(cond[i].value);
    break;
        case 2:
    diff = strcmp(value.c_str(), cond[i].value);
    break;
        }

        // skip the tuple if any condition is not met
        switch (cond[i].comp) 
        {
        case SelCond::EQ:
    if (diff != 0) goto next_tuple;
    break;
        case SelCond::NE:
    if (diff == 0) goto next_tuple;
    break;
        case SelCond::GT:
    if (diff <= 0) goto next_tuple;
    break;
        case SelCond::LT:
    if (diff >= 0) goto next_tuple;
    break;
        case SelCond::GE:
    if (diff < 0) goto next_tuple;
    break;
        case SelCond::LE:
    if (diff > 0) goto next_tuple;
    break;
        }
      }

      // the condition is met for the tuple. 
      // increase matching tuple counter
      count++;

      // print the tuple 
      switch (attr) 
      {
      case 1:  // SELECT key
        fprintf(stdout, "%d\n", key);
        break;
      case 2:  // SELECT value
        fprintf(stdout, "%s\n", value.c_str());
        break;
      case 3:  // SELECT *
        fprintf(stdout, "%d '%s'\n", key, value.c_str());
        break;
      }

      // move to the next tuple
      next_tuple:
      ++rid;
    }

    // print matching tuple count if "select count(*)"
    if (attr == 4) 
    {
      fprintf(stdout, "%d\n", count);
    }
    rc = 0;
  }

  // close the table file and return
  exit_select:
  rf.close();
  if (index_on)
    tree.close();
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  /* your code here */
	FILE* rdsrc;
	
	rdsrc = fopen(loadfile.c_str(), "r");
	
	if(rdsrc == NULL)
		return -1;
	
	int c;
	string line;
	int numlines;
	
	int key;
	string value;
	RecordId curRid;
	
	RecordFile::RecordFile rf = RecordFile::RecordFile(table + ".tbl", 'w');
	
	c = getc(rdsrc);
	
	BTreeIndex idx = BTreeIndex();
	
	if(index)
	{
		idx.open(table + ".idx", 'w');
	}
	
	while(c != EOF)
	{
		if((char)c == '\n')
		{
			parseLoadLine(line, key, value);
			rf.append(key, value, curRid);
			
			if(index)
			{
				int height = 0;
				int spid = 0, skey = 0; //initialization, not actually used
				int result;
				
				if(idx.insert(key, curRid, height, spid, skey) != 0)
				{
					return RC_NODE_FULL;
				}
			}
			
			numlines++;
			line = "";
		}
		else
		{
			line += (char)c;
		}
		
		c = getc(rdsrc);
	}
	
	if(line != "")
	{
		parseLoadLine(line, key, value);
		rf.append(key, value, curRid);
				
		if(index)
		{
			int height = 0;
			int spid = 0, skey = 0; //initialization, not actually used
			int result;
			
			if(idx.insert(key, curRid, height, spid, skey) != 0)
			{
				return RC_NODE_FULL;
			}
		}
	}
	else
	{
		if(numlines == 0)
			return -1; //empty file
	}
	
	rf.close();
	
	if(index)
	{
		idx.close();
	}
	
//	fclose(rdsrc);

	return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
